/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package canvaswork;

import geomerative.*;
import java.util.Set;
import processing.core.PApplet;

/**
 *
 * @author jonatanhilden
 */
class Grob implements Selectable {

    Main main;
    private RShape shp;
    private Group parent;
    public RPoint relativePosition; // relative relativePosition, used for transforms and to save the relativePosition values of grouped elements
    private RPoint[] handles; // cornerpoints of shape
    private RShape[] sides; // sides of shape
    int object_type;
    int xpos, ypos;
    int cx, cy;//to use for eventual center-point transformation
//    int rCx, rCy; // rotation centers, default set to shape center;
    int size;
    int width;
    int height;
    float scale = 1.0f;
    int mirror = 1;
    int rotation;
    int fillColor;
    int overColor = 150;
    int pressedColor = 0;
    boolean isDraggable = true;
    boolean intersects = false;

    Grob(Main main, Group parent, int id, int x, int y, int s, int r, int clr) {
        this.main = main;
        this.parent = parent;
        object_type = id;
        xpos = x;
        ypos = y;
        relativePosition = new RPoint();
        size = s;
        width = s;
        height = s;
        rotation = r;
        fillColor = clr;
        overColor = main.color(main.hue(clr), main.saturation(clr), ((main.brightness(clr)) / 2));
    }

    void update() { // draws the grobs to the canvas
        //update is a good place for resetting the state of the canvas Grobs
        createShp();

//        if (getSize()< main.gridSize)
//        {setSize(main.gridSize);}
        // Main.println("selected " + isSelected());
        //Main.println("pressed " + pressed);
//        if (!isSelected()) {
//            pressed = false;
//        }
    }

    void createShp() {
        getRelativePosition().x = 0;
        getRelativePosition().y = 0;
        // the object_id is retainded for the Grobs, and used to decide their "shape"
        if (object_type == 0) { // rectangle.
            shp = RShape.createRectangle(0, 0, width, height);
            sides = new RShape[4];

        } else if (object_type == 1) { // arc.
            shp = new RShape();
            sides = new RShape[5];


            // "fake" arc
            shp.addMoveTo(0, 0);
            shp.addBezierTo(width * 0.5522847498f, 0, width, height * 0.5522847498f, width, height);
            shp.addLineTo(0, width);
            shp.addClose();

            //circle:
            //shp = RShape.createCircle(0 + width / 2, 0 + size / 2, size / 2);

        } else if (object_type == 2) { // right-sided triangle
            shp = new RShape();
            sides = new RShape[3];

            shp.addLineTo(0, 0 + height);
            shp.addLineTo(0 + width, 0 + height);
            shp.addClose();

        } else if (object_type == 3) { // isosceles triangle
            shp = new RShape();
            sides = new RShape[3];
            shp.addLineTo(0, height);
            shp.addLineTo(height * (Main.sqrt(3) / 2), height / 2);
            shp.addClose();


        } else {
            Main.println("invalid object ID " + object_type); // TODO: Default shape?
        }
//        rotationCenter = getShape().getCenter();
    }

    void setupTransform() {
//        main.pushMatrix();
//
//
//        main.rotate(PApplet.radians(rotation));
//        main.scale(scale);
//        main.translate(xpos, ypos);

        //Matrix transformations
//        getShape().translate(getCenter());
        RPoint rotationCenter = main.getRotationCenter();



        getShape().scale(scale);
        //mirroring
        getShape().scale(mirror, 1, getCenter());
        getRelativePosition().rotate((PApplet.radians(rotation)));



        //positioning
        getRelativePosition().translate(getXpos(), getYpos());//change the methods to work directly with relativePosition?
        getShape().translate(relativePosition);
        getShape().rotate((PApplet.radians(rotation)), getCenter());
//        if (rotationCenter == null) {
//            getShape().rotate((PApplet.radians(rotation)), getCenter());
//        } else {
//            getShape().rotate((PApplet.radians(rotation)), rotationCenter);
//        }



        // creating sides at updated positions

        handles = getShape().getHandles();
        for (int i = 0; i < handles.length - 1; i++) {
            RShape s = new RShape();
            s.addMoveTo(handles[i]);
            s.addLineTo(handles[i + 1]);
            sides[i] = s;
        }

    }

    void draw() {


        //main.noFill();
        main.fill(fillColor);
        main.stroke(fillColor);

        setOverColor();
        setupTransform();


        //topLeft = getShape().getTopLeft();

        if (isDraggable) {
            //main.text("tl " + getTopLeftCorner().x, xpos, ypos + size + 10);
            //main.text(scale, xpos, ypos + size);
        }

        if (!isDraggable) {//undraggable objects are non-filled
            if (isSelected()) {
//                main.noFill();

                main.fill(fillColor);

                main.stroke(fillColor);


            } else if (main.isOver(this) || isTemporarySelected()) {

                main.fill(overColor);
                main.stroke(fillColor);

            } else {
//                main.noFill();
                main.fill(0);
                main.stroke(fillColor);
            }

        } else {// normal fill behaviour

            if (isSelected()) {
                main.noFill();
                if (isDraggingGrob()) {
                    main.text(relativePosition.x + " " + relativePosition.y, xpos, ypos);  // display coordinates
                }
//              optional display of sides
//                for (RShape s : sides) {
//                    main.stroke(250);
//                    if (s != null) {
//                        RG.shape(s);
//                    }
//                }


                //main.fill(fillColor);

                main.stroke(fillColor);
            } else if (main.isOver(this) || isTemporarySelected()) {
                main.fill(overColor);
                main.stroke(fillColor);




            } else {
                //main.noFill();
                main.fill(fillColor);
//                main.noStroke();

                main.stroke(fillColor);
            }
        }

        RG.shape(getShape());
        main.stroke(main.hue(getFillColor() - getFillColor()), 100, ((main.brightness(getFillColor()))));
        if (main.dragGrobSelectedSideNr != -1 && !(main.dragGrobSelectedSideNr > sides.length) && isDraggingGrob()) {
            RG.shape(sides[main.dragGrobSelectedSideNr]);//highlight the selected side 
//            main.ellipse(getSelectedSideHandle(0).x, getSelectedSideHandle(0).y, 5, 5);
        }
        if (main.closestGrobSideNr != -1 && main.snaptargetGrob == this) {
            RG.shape(sides[main.closestGrobSideNr]);//highlight the selected side
//            main.ellipse(getSelectedSideHandle(0).x, getSelectedSideHandle(0).y, 5, 5);
        }

    }

    void grobSelect() {// this function updates the selected state for a single Grob
        // Main.println("grobSelect");
        //possibly simplify using isTemporarySelected
        if (main.isOver(this)) {//select the grob under the mouse if one or no grobs are selected
            //  if several grobs are selected, deselect them and select the current.

            //  selecting a side.
            if (isDraggable) {
                // not needed in current setup:
//                selectSide();
            }

            if (main.shiftPressed) {// if shift is pressed, select or deselect clicked grob
                if (!isSelected()) {
                    main.select(this);
                } else {
                    main.deselect(this);

                }
            } else if (main.selectionSize() <= 1 || !isSelected()) {//TODO: getSize!!
                main.singleSelect(this);
            }
        }
    }

    boolean isSelected() {
        return main.isSelected(this);
    }

    boolean isTemporarySelected() {
        return main.isTemporarySelected(this);
    }

    public boolean isDraggingGrob() {
        return (main.dragGrob == this);
    }

    public boolean isGroup() {
        return false;
    }

    public void setParent(Group parent) {
        this.parent = parent;
    }

    public Group getParent() {
        return parent;
    }

    public boolean hasParent() {
        return parent != null;
    }

    public boolean hasChild(Grob grob) { // to enable testing the groups for selected children
        return false;
    }

    public boolean hasChildren() {
        return false;
    }

    public RShape getShape() {
        return shp;
    }

    public void setShape(RShape shape) {
        shp = shape;
    }

    public int getSize() {
        return size;
    }

    public int getWidth() {
        return width;
    }

    public void setScale(float scale) {
        this.scale = Main.constrain(scale, 0.1f, 9.0f);
    }

    public float getScale() {
        return scale;
    }

    public void setSize(int size) {
        this.size = Main.constrain(size, main.gridSize, main.canvasWidth);

    }

    public boolean isIsDraggable() {
        return isDraggable;
    }

    public void setIsDraggable(boolean isDraggable) {
        this.isDraggable = isDraggable;
    }

    boolean contains(RGeomElem shape) {//test whether a shape is contaied or intersects
        return (shape.intersects(getShape()) || shape.containsBounds(getShape()));
    }

    boolean intersects(RGeomElem shape) {
        return (shape.intersects(getShape()));
    }

    boolean intersects(Grob grob) {//intersect method for two grobs
        return (grob.getShape().intersects(getShape()));
    }

    int sideIntersects(RGeomElem shape) {
        int sidenum = 100;//null value
        for (int i = 0; i < sides.length; i++) {
            if (sides[i] != null) {
                if (sides[i].intersects(shape)) {
                    sidenum = i;
                }
            }
        }
        return sidenum;
    }

    public RShape[] getSides() {
        return sides;
    }

    public RShape getSide(int s) {
        if (s <= sides.length - 1 && s != -1) {
            return sides[s];
        } else {
            return sides[0];
        }
    }
    //  selecting a side. When the user clicks a grob, this method
    //  calculates which side of the grob is closest to the click
    //  sideNumber is updated with the number of the side.

    public int selectSide(int accuracy) {
        float[] distances;
        distances = new float[sides.length];
        int selectedSideNr = -1;// side number
        for (int i = 0; i < sides.length; i++) {
            if (sides[i] != null) {//   calculate the distance between a point(mouse) and a line in sides;
                distances[i] = main.distanceToInfiniteLine(main.mouseX, main.mouseY, Math.round(sides[i].getHandles()[0].x),
                        Math.round(sides[i].getHandles()[0].y), Math.round(sides[i].getHandles()[1].x), Math.round(sides[i].getHandles()[1].y));
            } else return selectedSideNr;
            
        }

        float dmin = Main.min(distances);
            // getting out the index of the minimum value for choosing the right side
            for (int j = 0; j < distances.length; j++) {
                if (dmin == distances[j] && dmin < accuracy) {
                    selectedSideNr = j;
                }
            }
        return selectedSideNr;
    }

    public RPoint[] getSideHandles(int s) { // returns the handles of the selected side
        RPoint[] sideHandles;
        sideHandles = new RPoint[2];
        if (s != -1) {
            sideHandles[0] = handles[s];
            sideHandles[1] = handles[s + 1];
        }

        return sideHandles;
    }

    public RPoint[] getHandles() {
        return handles;
    }

    public RPoint getHandle(int h) {
        if (h <= sides.length - 1 && h != -1) {
            return handles[h];
        } else {
            return handles[0];
        }
    }

    public int getXpos() {
        return xpos;
    }

    public void setXpos(int x) {
        this.xpos = x;
    }

    public int getYpos() {
        return ypos;
    }

    public void setYpos(int ypos) {
        this.ypos = ypos;
    }

    void setPosition(int x, int y) {//sets xpos and ypos definers
        xpos = x;
        ypos = y;
    }

    public RPoint getRelativePosition() {
        return this.relativePosition;
    }

    void setRelativePosition(int x, int y) {//set the RPoint "relativePosition"
        relativePosition.x = x;
        relativePosition.y = y;
    }

       void translateRelativePosition(int x, int y) {
        relativePosition.translate(x, y);
    }

    public RPoint getCenter() {
        return getShape().getCenter();
    }

    public int getFillColor() {
        return fillColor;
    }

    public void setFillColor(float h, float s, float b) {

        this.fillColor = main.color(h, s, b);


    }

    public void setFillColor(int fill) {
        this.fillColor = fill;
    }

    public void setOverColor() {
        this.overColor = main.color(main.hue(getFillColor()), main.saturation(getFillColor()), ((main.brightness(getFillColor())) / 1.8f));
    }

    public void resize(int sizeChange) {
        setSize(getSize() + sizeChange);
    }

    public void scale(float s) {
        setScale(getScale() + s);

    }

    public void rotateGrob(int degrees) {
        rotation = rotation + degrees;
    }

    public int getRotation() {
        return rotation;
    }

    public void setRotation(int degrees) {
        this.rotation = degrees;
    }

    void mirrorGrob() {
        mirror = -mirror;
        //setXpos((int) getTopLeftCorner().x);

    }

    public Grob copyGrob(Group newParent, int x, int y, int xx, int yy) {
        //returns a copy of the current grob at x, y -min_X, min_y
        //to retain relative relativePosition if multiple grobs are selected

        Grob grob = new Grob(main, newParent, object_type, ((x / main.gridSize) * main.gridSize) + xpos - xx,
                ((y / main.gridSize) * main.gridSize) + ypos - yy, size, rotation, fillColor);
        grob.setScale(getScale());
        grob.mirror = mirror;
        grob.update();
        return grob;
    }

    public Grob copyGrob(Group newParent, int colorMod) {
        //returns a copy of the current grob at x, y -min_X, min_y
        //to retain relative relativePosition if multiple grobs are selected

        Grob grob = new Grob(main, newParent, object_type, xpos, ypos, size, rotation, fillColor - fillColor / colorMod);
        grob.rotation = rotation;
        grob.setScale(getScale());
        grob.mirror = mirror;

        return grob;
    }

    void addToSelection(Set<Grob> selection, RGeomElem shape) {//currently unused
        if (contains(shape)) {
            selection.add(this);
        }
    }

    void removeFromSelection(Set<Grob> selection, RGeomElem shape) {//currently unused
        if (contains(shape)) {
            selection.remove(this);
        }
    }
}
